﻿// === Copyright 2008-2009 Jamie Macia et al. ===
// Module:      DiceRoll
// Created:     11/18/2008
// Author:      Jamie Macia
// Description: A base class related to the rolling of "dice" in the context of d20 gaming
// Revisions:
//      *jmacia 11/18/08 Issue 1: Created

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


namespace JMacia.Gaming.d20.Core.Dice
{
    /// <summary>
    /// This struct represents a set of polyhedral dice.
    /// </summary>
    public struct DiceSet
    {
        #region Private Members

        private int _number;
        private int _sides;
        
        #endregion

        #region Constructors

        /// <summary>
        /// Constructor for a single-die set
        /// </summary>
        /// <param name="sides">The number of sides on the die</param>
        public DiceSet(int sides)
        {
            _sides = sides;
            _number = 1;
        }

        /// <summary>
        /// Constructor for any number of dice
        /// </summary>
        /// <param name="number">The number of dice in the set</param>
        /// <param name="sides">The number of sides on each die</param>
        public DiceSet(int number, int sides)
        {
            _sides = sides;
            _number = number;
        }
        
        #endregion

        #region Public Properties

        /// <summary>
        /// The number of dice in the set
        /// </summary>
        public int Number
        {
            get
            {
                return _number;
            }
            set
            {
                _number = value;
            }
        }

        /// <summary>
        /// The number of sides on each die
        /// </summary>
        public int Sides
        {
            get
            {
                return _sides;
            }
            set
            {
                _sides = value;
            }
        }
        
        #endregion

        #region Public Methods

        /// <summary>
        /// Represents the set of dice as a string
        /// </summary>
        /// <returns>"XdY", where X is the number of dice and Y is the number of sides per die</returns>
        /// <example>Two six-sided dice (DiceSet(2,6)) would be represented as "2d6"</example>
        public override string ToString()
        {
            return Number.ToString() + "d" + Sides.ToString();
        }

        /// <summary>
        /// Quickly rolls a single set of dice.
        /// </summary>
        /// <returns>The total value of all the dice rolled</returns>
        public int Roll()
        {
            DiceRoll roll = new DiceRoll(this);
            return roll.Roll();
        }
        
        #endregion
    }

    /// <summary>
    /// A base class to represent and roll an arbitrary number of dice plus or minus an integral modifier
    /// </summary>
    public class DiceRoll
    {
        #region Protected Members

        protected List<DiceSet> _dice = new List<DiceSet>();
        protected static Random _rand = new Random();
        protected int _modifier;
        
        #endregion

        #region Constructors

        /// <summary>
        /// Empty constructor.
        /// </summary>
        public DiceRoll() { }

        /// <summary>
        /// Constructs a DiceRoll from a properly formatted string
        /// </summary>
        /// <param name="rollDefinition">The string to be parsed</param>
        /// <seealso cref="Parse(string)"/>
        public DiceRoll(string rollDefinition)
        {
            Parse(rollDefinition);
        }

        /// <summary>
        /// Constructs a DiceRoll from one or more DiceSets
        /// </summary>
        /// <param name="sets">One or more sets of dice to be rolled</param>
        public DiceRoll(params DiceSet[] sets)
        {
            AddDice(sets);
        }

        /// <summary>
        /// Constructs a DiceRoll from one or more DiceSets and an integral modifier
        /// </summary>
        /// <param name="modifier">The modifier to apply to the total roll</param>
        /// <param name="sets">One or more sets of dice to be rolled</param>
        public DiceRoll(int modifier, params DiceSet[] sets)
        {
            _modifier = modifier;
            AddDice(sets);
        }

        /// <summary>
        /// Copy constructor
        /// </summary>
        /// <param name="copy">The DiceRoll of which to make a copy</param>
        public DiceRoll(DiceRoll copy)
        {
            this._dice = new List<DiceSet>(copy._dice);
            this._modifier = copy._modifier;
        }
        
        #endregion

        #region Public Properties

        /// <summary>
        /// Parses a string 
        /// </summary>
        /// <param name="rollDefinition">
        /// A string containing the expression to be rolled.
        /// This should be of the form "UdV+XdY+...+Z", 
        /// where U-Y are positive integers and Z is an integer.  
        /// Spaces are ignored.</param>
        /// <exception cref="ArgumentException">Thrown if a number within the string cannot be parsed</exception>
        /// <seealso cref="int.Parse(string)"/>
        public virtual void Parse(string rollDefinition)
        {
            StringBuilder builder = new StringBuilder(rollDefinition);
            builder.Replace("-", "+-");
            builder.Replace(" ", string.Empty);

            string[] pieces = builder.ToString().Split('+');
            foreach (string piece in pieces)
            {
                if (string.IsNullOrEmpty(piece))
                {
                    continue;
                }

                int sides, numDice, pos = piece.IndexOf("d", StringComparison.InvariantCultureIgnoreCase);
                if (pos > 0)
                {
                    try
                    {
                        numDice = int.Parse(piece.Substring(0, pos));
                        sides = int.Parse(piece.Substring(pos + 1));
                    }
                    catch (Exception e)
                    {
                        throw new ArgumentException("Could not parse the dice to be rolled: " + piece, e);
                    }

                    AddDice(numDice, sides);
                }
                else if (pos == 1)
                {
                    numDice = 1;
                }
                else
                {
                    try
                    {
                        this._modifier += int.Parse(piece);
                    }
                    catch (Exception e)
                    {
                        throw new ArgumentException("Could not parse the modifier: " + piece, e);
                    }
                }
            }
        }

        /// <summary>
        /// Adds dice to the roll.
        /// </summary>
        /// <param name="numDice">Number of dice to be added</param>
        /// <param name="dieSides">Number of sides per die to be added</param>
        public void AddDice(int numDice, int dieSides)
        {
            AddDice(new DiceSet(numDice, dieSides));
        }

        /// <summary>
        /// Adds sets of dice to the roll.
        /// </summary>
        /// <param name="sets">Sets of dice to be added</param>
        public virtual void AddDice(params DiceSet[] sets)
        {
            _dice.AddRange(sets);
        }

        /// <summary>
        /// Rolls the dice.
        /// </summary>
        /// <returns>The total value of the dice and the modifier</returns>
        public virtual int Roll()
        {
            List<int> dummy;
            return Roll(out dummy);
        }

        /// <summary>
        /// Rolls the dice.
        /// </summary>
        /// <param name="individualRolls">
        /// A list to be populated with the values of the individual rolls</param>
        /// <returns>The total value of the dice and the modifier</returns>
        public virtual int Roll(out List<int> individualRolls)
        {
            individualRolls = new List<int>();
            int total = _modifier;
            foreach (DiceSet set in _dice)
            {
                for (int i = 0; i < set.Number; i++)
                {
                    int roll = _rand.Next(1, set.Sides + 1);
                    total += roll;
                    individualRolls.Add(roll);
                }
            }
            return total;
        }

        /// <summary>
        /// Represents the group of dice as a string.
        /// </summary>
        /// <returns>
        /// A string representing the dice to be rolled.
        /// This will be of the form "UdV+XdY+...+Z", 
        /// where U-Y are positive integers and Z is an integer.</returns>
        public override string ToString()
        {
            StringBuilder build = new StringBuilder();
            for (int i = 0; i < _dice.Count; i++)
            {
                build.Append(_dice[i].ToString());
                if (i < _dice.Count - 1)
                {
                    build.Append(" + ");
                }
            }

            if (_modifier < 0)
            {
                build.Append(" - ");
                build.Append(-_modifier);
            }
            else if (_modifier > 0)
            {
                build.Append(" + ");
                build.Append(_modifier);
            }

            return build.ToString();
        }
        
        #endregion

        #region Operators
		
	    #endregion
    }
}
